home *** CD-ROM | disk | FTP | other *** search
Text File | 1994-12-04 | 19.3 KB | 592 lines | [TEXT/EDIT] |
- //• ------------------------------------------------------------------- •//
- //• Another public domain C example source demo, brought back from •//
- //• the dead at: itty bitty bytes™ - by Kenneth A. Long! •//
- //• Made to run in Think C™ on 2 June 1994. •//
- //• Uses no resource file - just add MacTraps and ANSI libraries. •//
- //• The original of this file was found on ftp.cso.uiuc.edu in mac/MUG. •//
- //• ------------------------------------------------------------------- •//
-
- //• 3D Demonstration (DRAFT)
- //• as told in Megamax C V2.1
- //• by
- //• M H Miller ... 30 March 1986.
-
- //• wire frame 10 April 1986.
- //• rotations 15 April 1986.
- //• visibility 1 May 1986.
- //• touchup 3 May 1986.
- //• add 1st order lighting 8 May 1986.
-
- //• This is an 'application' which eventually will take its proper
- //• place among a set of exercises whose purpose is enhancing
- //• understanding of C programming (Megamax C is the particular vehicle
- //• used), and in facilitating oozing through Inside Macintosh. In
- //• most of these exercises the methodology is to explore a number
- //• of Inside Macintosh functions and procedures, observe their visual
- //• effects (if any), study their syntax, and in general evaluate their
- //• use and usfulness in the Macintosh interface. Modifications,
- //• enhancements, non_commercial uses of this material, and tactful
- //• comments are invited, indeed encouraged.
-
- //• In studying the exercise (s) keep in mind (for my sake at least)
- //• that the principal intention is not to optimize the operation (s)
- //• performed but rather to examine Macintosh operations and C
- //• programming. With that view what you don't care for should be
- //• considered an objective illustration of what not to do.
-
- //• --------------------------------------------------------
- //• This exercise involves computation of the isometric projection of a
- //• solid and rotation of that solid about an axis. The character of
- //• the solid is constrained so that particular circumstances requiring
- //• extended computation and manipulation are avoided. Specifically the
- //• solid must have plane faces which are closed polygons. The solid
- //• must be 'convex', i.e., the angle between outward normals to two
- //• contiguous faces must be greater than 90 degrees; this eliminates
- //• solids with 'holes', indentations, or 'pimples'. For a convex
- //• solid a face either is visible in its entirety or it isn't; there
- //• is no 'shadowing' of one face by others. Rotations of the solid
- //• are about a single (coordinate) axis at a time for illustrative
- //• simplicity, although generalizing is not all that difficult or
- //• involved. For each rotated position an equiangular isometric
- //• projection is computed. In addition the orientation of each face
- //• of a projection is computed to determine if the outward normal of
- //• a projected face points into or out of the screen; a face is visible
- //• looking into the screen if its outward normal points out of the
- //• screen. A dynamic presentation of the rotating solid is drawn,
- //• optionally either as a wire frame or as a solid. Additional
- //• variations are planned for addition now and then.
-
- //• NOTE 1: The nature of the data structures used is of some importance.
- //• However no great emphasis was placed on optimizing these in this
- //• illustration. Consider this illustration to be an illustration and
- //• not a 'how to' prescription. Nevertheless the program is written to
- //• allow easy redefinition of the object rotated, within the simplifying
- //• constraints noted.
-
- //• NOTE 2: This is a 'brute force' version to provide a reference against
- //• which to evaluate the efficacy of measures to reduce flickering and
- //• image discontinuity. Some modifications to be added sooner or later
- //• are bitmap switching, assembly language interventions, synchronizing
- //• to the blanking interrupts, and cleaning the screen as a VBL task.
- //• --------------------------------------------------------.
-
- #include <stdio.h>
- #include <math.h>
-
- //typedef int void; //• Thoroughly Modern Miller.
-
- #define X 0 //• Axis references.
- #define Y 1
- #define Z 2
-
- //• For simplicity the object to be drawn is predefined. The
- //• specification is for the 'base' position from which all rotated
- //• positions are computed. Within the constraints on the type of
- //• object allowed a new object can be redefined by redefining the
- //• parameters asdescribed or readily inferred. The following three
- //• parameters are used to set computation loop limits; redefine to suit.
-
- #define NMBR_FACES 9
- #define NMBR_VERTICES 9
- #define NMBR_SETS 20 //• rotation angle step == 360 / NMBR_SETS.
-
-
- //• Object vertex coordinates (relative to vertex 0). This example is
- //• for a regulartrapazoidal parallelpiped. This part of the
- //• specification only partially defines thesolid; still to come is
- //• specification of the face edges, i.e., the connections between
- //• vertices which define the individual faces. Dimensions are
- //• referenced to LX, LY, and LZ. Note that rotations will be about
- //• coordinate axes for simplicity. However that does not constrain
- //• the initial orientation of the object; it can be 'tilted' as desired.
-
- #define LX 80
- #define LY 40
- #define LZ 60
-
- //• #####################################
- //• (Try using this vertex table, and comment out the other one - K.A.L.)
- //• 'Plain Jane' initial orientation (not used)
- /*
- int vertex [NMBR_VERTICES] [3] =
- {
- { 0, 0, 0 },
- { 0, 0, LZ },
- { LX, 0, LZ },
- { LX, 0, 0 },
- { 3 * LX / 4, LY, LZ / 4 },
- { 3 * LX / 4, LY, 3 * LZ / 4 },
- { LX / 4, LY, 3 * LZ / 4 },
- { LX / 4, LY, LZ / 4 },
- { LX / 2, -LY / 2, LZ / 2 }
- };
- */
- //• ####################################
-
-
- //• 'Centerfold' (more exposed) initial position;
- //• scale not the same as for Plain Jane.
- //• This is the specification used for the program.
-
- int vertex [NMBR_VERTICES] [3] =
- {
- { 0, 0, LZ / 2 },
- { LX / 2, 0, LZ },
- { LX, 0, LZ / 2 },
- { LX / 2, 0, 0 },
- { LX / 2, LY, LZ / 4 },
- { 3 * LX / 4, LY, LZ / 2 },
- { LX / 2, LY, 3 * LZ / 4},
- { LX / 4, LY, LZ / 2 },
- { LX / 2, -LY / 2, LZ / 2 }
- };
-
-
- //• A face is defined its edges; in turn these are defined by a set
- //• of vertices. The vertex list is determined as follows:
-
- //• a) Imagine the outward normal to the surface to be drawn.
-
- //• b) Imagine your right hand curled around the normal, thumb
- //• pointing outward from theface along the normal.
-
- //• c) Follow the curl of your fingers in listing the vertices
- //• forming the face edges. Note that the last vertex listed is
- //• the same as the first one; any of the face vertices maybe 'first'
- //• in the list.
-
- //• The number of vertices need not be the same for all faces. Note
- //• again that faces are required to be planar polygons. Violation
- //• of this constraint generally shows up as overlapping faces as
- //• the object rotates.The following 'face' specification is for the
- //• trapazoidal pyramid. Per standard C rules any array elements not
- //• specified will be set to 0, although this is not important since
- //• they are not used at all.
-
- int face [NMBR_FACES] [5] =
- {
- {0, 1, 6, 7, 0},
- {1, 2, 5, 6, 1},
- {2, 3, 4, 5, 2},
- {7, 4, 3, 0, 7},
- {7, 6, 5, 4, 7},
- {0, 3, 8, 0},
- {8, 1, 0, 8},
- {8, 2, 1, 8},
- {8, 3, 2, 8}
- };
-
- //• Define the coordinates xr, yr of the center of rotation/origin (this
- //• will be taken to be somewhere in the plane of the screen), and the
- //• offset of vertex 0 from the center of rotation (dxo, dyo, dzo).
- //• Thus the absolute location of, say the X-coordinate of vertex 4, is
- //• xr + dxo + vertex [4] [X]. See below for the computation of
- //• projection coordinates.
-
- int xr = 200, yr = 160, zr = 0, dxo = 50, dyo = 30, dzo = -50;
-
- //• For each face a polygon will be computed (later) for the
- //• projection of that face.
- PolyHandle face_poly [NMBR_SETS] [NMBR_FACES];
-
- //• For each face, and in each set the direction of the normal to a
- //• projected face is computed to determine if that face is visible
- //• or not. In addition the illumination (yes or no) of a face from
- //• a source to the left and up is determined.
-
- short visible [NMBR_SETS] [NMBR_FACES];
- short light [NMBR_SETS] [NMBR_FACES];
-
- short drawflag; //• Switch between drawing types. SOLID illustrates
- //• the use of the 'visibility'computation, i.e., only
- //• 'visible' faces are drawn. SOLID_LIGHT adds
- //• first-order illumination.
-
- #define SOLID_LIGHT 0
- #define SOLID 1
- #define WIRE_FRAME 2
-
- Rect option_rect, display_rect; //• Housekeeping.
-
- WindowPtr newWindow; //• Added by K.A.L.
- Rect dragRect; //• Added by K.A.L.
- Rect windowBounds = { 20, 0, 384, 512}; //• Added by K.A.L.
-
- //• Prot's:
- int main (void);
- void Compute_Rotation_Data (int axis);
- void Rotate_Object (void);
-
- main ()
- {
- EventRecord the_event;
- Point mousepoint;
- GrafPtr the_port;
- int i, j;
- register int this_set, next_set;
- Rect temp_rect;
-
- InitGraf (&thePort);
- InitFonts ();
- InitWindows ();
- InitCursor ();
-
- //• The next 3 lines was added by K.A.L. because...
-
- dragRect = qd.screenBits.bounds;
-
- newWindow = NewWindow (0L, &windowBounds, "\p", true,
- noGrowDocProc, (WindowPtr) -1L, true, 0);
- SetPort (newWindow);
-
- //• ...the next 6 lines messed things up.
-
- //• Use the window manager port (desktop) directly.
- //• Open up the clipregion to include themenu bar and clear the screen.
- //• Define a housekeeping rectangle for erasing the screen.
-
- // GetWMgrPort (&the_port);
- // SetPort (the_port);
- // ClipRect (& (the_port->portRect));
- // EraseRect (& (the_port->portRect));
- // SetOrigin (0, 0);
-
- //• K.A.L. made the following be windowBounds (above).
- // SetRect (&display_rect, 0, 21, 512, 342);
-
- //• Startup with SOLID_LIGHT, X-axis rotation.
- Compute_Rotation_Data (X);
- drawflag = SOLID_LIGHT;
-
-
- for (;;)
- {
- GetNextEvent (everyEvent, &the_event);
- if (the_event.what == mouseDown )
- {
- GetMouse (&mousepoint);
-
- if (PtInRect (mousepoint, &option_rect) )
- {
- switch (i = mousepoint.h / 15)
- {
- case 0: //• Q for QUIT.
- exit (0);
- break;
-
- case 1:
- case 2:
- case 3: //• Rotate X, Y, Z respectively.
- Compute_Rotation_Data (i-1);
- break;
-
- case 4: //• SOLID, SOLID_LIGHT, WIRE_FRAME.
- if (++drawflag == 3) drawflag = 0;
- break;
- }
- }
- else
- while (Button ()) ; //• Freeze frame.
- }
- Rotate_Object (); //• Take a step.
- }
- }
-
- //• The isometric projection is an equiangular proection on the
- //• plane (screen). The y-axis coordinate of a point is drawn
- //• vertically (+ down) from the origin. The x-axis position is
- //• along a Line drawn 30 degrees down from horizontal, while the
- //• z-axis is drawn along a Line elevated 30 degrees above the
- //• horizontal. The net result is that the screen position relative
- //• to the origin of the point x, y, z is (in screen position
- //• coordinates) (x, y) = (x + z) * cos (30), y + (x - z) * sin (30)
- //• This procedure computes the rotated position of the vertices,
- //• computes whether a projected face is 'visible' or not, and
- //• defines polygons for each face in each projection. The data
- //• produced is 'face_poly [this_set] [i]' which is the polygon
- //• for drawing face #i after a rotation of 360 * this_set / NMBR_SETS.
- //• No indication provided for axis of rotation. In addition
- //• 'visible [this_set] [i]' =1/0 indicates that face is visible / invisible
- //• seen looking into the screen.
-
- //• Note: The only global parameters provided by this illustrative
- //• computation are the polygon handles and the 'visibility' and
- //• 'light' arrays.
- void Compute_Rotation_Data (int axis)
- {
- register int this_set, i, j; //• Rotation angle is 360 * this_set / NMBR_SETS.
- int basev [NMBR_VERTICES] [3]; //• Vertex coordinates offset from center of rotation.
- int v [NMBR_VERTICES] [3]; //• Vertex rotated coordinates.
- int p_vx [NMBR_VERTICES], p_vy [NMBR_VERTICES]; //• Projection coordinates.
- int ax, ay, az, bx, by, bz; //• Vector components for computing direction cosines.
-
- //• Miscellaneous utility variables•//
- Rect count_rect;
- int v0, v1, v2;
- float sin_table, cos_table;
- #define D_THETA 6.283185 / NMBR_SETS
-
- //• Cf Psych 100 notes.
- SetRect (&count_rect, 0, 21, 512, 342);
- EraseRect (&count_rect);
- TextSize (18);
- TextFace (bold);
- TextFont (geneva);
- PenNormal ();
- MoveTo (200, 150);
- DrawString ("\pHOLD ON");
- MoveTo (50, 170);
- DrawString ("\pI'M COMPUTING WHAT I NEED TO KNOW");
-
- //• Compute the rotated position of the vertices. Note that the
- //• computation references the base vertex definitions for each
- //• computation rather than accumlating incremental rotations.
- //• For real time rotation computations the latter would be
- //• appropriate, provided regular reinitialization, direct or
- //• indirect, avoided error accumulation.
-
- //• Locate vertices relative to center of rotation.
- for (i = 0; i < NMBR_VERTICES; ++i)
- {
- basev [i] [X] = vertex [i] [X] + dxo;
- basev [i] [Y] = vertex [i] [Y] + dyo;
- basev [i] [Z] = vertex [i] [Z] + dzo;
- }
-
- //• Angular increment is 360 / NMBR_SETS.
- for (this_set = 0; this_set < NMBR_SETS; ++this_set)
- { //• Convenience variables.
- sin_table = sin (D_THETA * this_set);
- cos_table = cos (D_THETA * this_set);
-
- //• Rotate the vertices. Note that the coordinates are relative to the center of rotation.•//
- for (i=0;i<NMBR_VERTICES;++i)
- {
- switch (axis)
- { case Z:
- v [i] [X] = basev [i] [X] *
- cos_table - basev [i] [Y] *
- sin_table;
-
- v [i] [Y] = basev [i] [X] *
- sin_table + basev [i] [Y] *
- cos_table;
-
- v [i] [Z] = basev [i] [Z];
- break;
-
- case Y:
- v [i] [X] = basev [i] [X] *
- cos_table + basev [i] [Z] * sin_table;
-
- v [i] [Y] = basev [i] [Y];
- v [i] [Z] = -basev [i] [X] *
- sin_table + basev [i] [Z] * cos_table;
- break;
-
- case X:
- v [i] [X] = basev [i] [X];
- v [i] [Y] = basev [i] [Y] *
- sin_table + basev [i] [Z] * cos_table;
- v [i] [Z] = -basev [i] [Y] *
- cos_table + basev [i] [Z] * sin_table;
- break;
- }
- }
-
- //• A source of light is assumed far away and to the right. A
- //• face is illuminated if 'sees'the light, i.e., if its outward
- //• normal is directed to the right. In addition of course
- //• the face will have to be visible in the projection, as
- //• computed later. This illumination algorithm does not
- //• provided graded lighting as would occur in reality. A
- //• face either is or is not illuminated.
-
- //• Note: See comments below for 'visible' computation for
- //• additional pertinent remarks not included here.
- for (i = 0; i < NMBR_FACES; ++i)
- {
- v0 = face [i] [0];
- v1 = face [i] [1];
- v2 = face [i] [2];
- ay = v [v2] [Y] - v [v1] [Y];
- by = v [v0] [Y] - v [v1] [Y];
- az = v [v2] [Z] - v [v1] [Z];
- bz = v [v0] [Z] - v [v1] [Z];
-
- //• An illuminated face has a 0 flag..
- light [this_set] [i] = (ay * bz - az * by) > 0 ?0 :1;
- }
-
- //• Compute the isometric positions for each vertex in the
- //• current set. The projection is computed relative to the
- //• center of rotation as origin and then reset relative to
- //• the screen origin.
- for (i = 0; i < NMBR_VERTICES; ++i)
- {
- p_vx [i] = xr + .86603 * (v [i] [X] + v [i] [Z]);
- p_vy [i] = yr + v [i] [Y] + (v [i] [X] - v [i] [Z])/2;
- }
-
- //• A face of the projection is 'visible' if it can be seen
- //• looking into the screen. To determine visibility use the
- //• fact that faces are defined by the array face [] [4] which
- //• provides the clockwise sequence of vertices defining the
- //• face. The procedure is:
-
- //• a) Select three consecutive
- //• vertices #0, #1, #2 from the face definition vector to
- //• define two projected face edges. The first three are used.
- //• Note that the face definition vector lists vertices
- //• circulating CCW about outward directed normal.
-
- //• b) Define edge#1 as fron vertex #1 to vertex #2; call this
- //• edge A.
-
- //• c) Define edge#2 as fron vertex #1 to vertex #0; call this
- //• edge B.
-
- //• d) The normal is A x B = Ax * By - Ay * Bx;
- //• if this is negative (out of the screen) the face is visible.
-
- //• Note: Notational guide: The first three vertices of face #
- //• this_set are face [this_set] [0], face [this_set] [1], and
- //• face [this_set] [2]. Compute components as:
- //• Ax = v [face [this_set] [2]] [X] -
- //• v [face [this_set] [1]] [X] ;
- //• Bx = v [face [this_set] [0]] [X] -
- //• v [face [this_set] [1]] [X] ;
- //• The fact that the vertex coordinates are relative to the
- //• center of rotation washes out because of the difference is
- //• taken.
-
- for (i = 0; i < NMBR_FACES; ++i)
- {
- v0 = face [i] [0];
- v1 = face [i] [1];
- v2 = face [i] [2];
- ax = p_vx [v2] - p_vx [v1];
- bx = p_vx [v0] - p_vx [v1];
- ay = p_vy [v2] - p_vy [v1];
- by = p_vy [v0] - p_vy [v1];
-
- //• Condition a flag 'visible [this_set] [i]' to indicate
- //• visible faces..
- visible [this_set] [i] = (ax * by - ay * bx) < 0 ?1 :0;
- }
-
- //• Compute the face polygons. Note that restriction to
- //• convex solids means a face either is visible or it is not;
- //• no partial visibility because of shadowing.
- for (i = 0; i < NMBR_FACES; ++i)
- {
- face_poly [this_set] [i] = OpenPoly ();
- MoveTo (p_vx [face [i] [0]], p_vy [face [i] [0]] );
- for (j = 1; j < NMBR_VERTICES; ++j)
- {
- LineTo (p_vx [face [i] [j]], p_vy [face [i] [j]] );
- if (face [i] [j] == face [i] [0] )
- break;
- }
- ClosePoly ();
- }
-
- //• Display computational progress as a countdown.
- SetRect (&count_rect, 200, 175, 250, 205);
- EraseRect (&count_rect);
- MoveTo (210, 200);
- i = (NMBR_SETS-this_set);
- if (i > 99)
- DrawChar (48 + i / 100);
- else
- DrawChar (' ');
-
- if (i > 99)
- {
- i %= 100;
- DrawChar (48 + i / 10);
- i %= 10 ;
- }
- else
- {
- if (i>9)
- {
- DrawChar (48 + i / 10);
- i %= 10;
- }
- else
- DrawChar (' ');
- }
- DrawChar (48+i);
- }
-
- //• Draw 'option bar'.
- TextSize (12);
- SetRect (&option_rect, 0, 0, 76, 20);
- FrameRect (&option_rect);
-
- MoveTo (5, 16);
- DrawChar ('Q');
-
- MoveTo (0, 15);
- Line (0, 15);
-
- MoveTo (20, 16);
- DrawChar ('X');
-
- MoveTo (0, 30);
- Line (0, 15);
-
- MoveTo (35, 16);
- DrawChar ('Y');
-
- MoveTo (0, 45);
- Line (0, 15);
-
- MoveTo (50, 16);
- DrawChar ('Z');
-
- MoveTo (0, 60);
- Line (0, 15);
-
- MoveTo (65, 16);
- DrawChar ('F');
- }
-
- void Rotate_Object ()
- {
- register int i;
- static int set = 0;
- register PolyHandle *poly;
-
- poly = &face_poly [set] [0];
-
- EraseRect (&windowBounds);
- if (drawflag == SOLID_LIGHT)
- {
- for (i=0;i<NMBR_FACES;++i)
- {
- if (visible [set] [i] ==1)
- {
- if (light [set] [i] ==1)
- FillPoly (* (poly + i), <Gray);
- FramePoly (* (poly + i));
- }
- }
- }
- else
- if (drawflag == SOLID)
- {
- for (i = 0; i < NMBR_FACES; ++i)
- if (visible [set] [i] ==1)
- FramePoly (* (poly + i));
- }
- else
- for (i = 0; i < NMBR_FACES; ++i)
- FramePoly (* (poly + i));
-
- if (++set == NMBR_SETS )
- set = 0; //• Update the rotation angle.
- }
-
-